#pragma once
#include <iostream>
#include <memory>
#include <ctime>
#include <unistd.h>
#include <sys/types.h>
#include <filesystem>
#include <fstream>
//#include <sys/types.h>
#include <sys/wait.h>

#define SIZE 1024

static std::string GetNowTime()
{
    time_t t = time(nullptr);         // 得到时间戳
    struct tm *ctime = localtime(&t); // 转化为结构体
    char timebuf[SIZE] = {0};
    snprintf(timebuf, sizeof(timebuf), "[%d-%d-%d %d:%d:%d]",
             ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec); // 格式化时间戳

    return timebuf;
}

////////////////////////////////////责任链模式的消息队列//////////////

// 任务一：将发送的消息格式化 [时间] + [进程pid] + [消息]
// 任务二：将发送的消息保存到目录当下tmp中路径：./tmp/...
// 任务三：当发送的消息达到一定程度时（比如发送信息超过5条行数，我们就备份一个文件）
// 任务四：我们将备份的文件进行打包，删除原文件

class HandlerText // 责任链基类
{
public:
    void Enable() { _enable = true; }
    void Disable() { _enable = false; }
    void SetNext(const std::shared_ptr<HandlerText> &next) { _next = next; }
    virtual void Execute(const std::string &text) = 0; // 接口
protected:
    std::shared_ptr<HandlerText> _next = nullptr; // 指向下一个任务
    bool _enable = true;                          // 是否启动改任务点
};

// 任务一：格式化信息
// "hello" —> "[时间][pid]：hello"
class HandlerFormat : public HandlerText
{
public:
    virtual void Execute(const std::string &text) override
    {
        std::string content = text;
        if (_enable)
        {
            content = GetNowTime() + '[' + std::to_string(getpid()) + "]: " + text;
            std::cout << "step 1: format text : " << content << " done..." << std::endl;
        }
        // 判断下一个任务节点是否存在
        if (_next)
        {
            _next->Execute(content);
        }
        else
        {
            std::cout << "Chain Of Responsibility Done..." << std::endl;
        }
    }
};

// 任务二：将文本内容写入到文件中
// 文件路径:"./tmp/"
// 文件名称:"text.log"
const std::string defaultfilepath = "./tmp/";
const std::string defaultfilename = "text.log";
class HandlerSave : public HandlerText
{
private:
    std::string _filepath;
    std::string _filename;

public:
    HandlerSave(const std::string &filepath = defaultfilepath, const std::string &filename = defaultfilename)
        : _filepath(filepath), _filename(filename)
    {
        // 无法保证，路径是正确的/路径是已经存在的。
        //  所以，在进行执行之前，我们在构造该任务点的时候，就把路径创建成功 —> C++17语法
        if (std::filesystem::exists(_filepath)) // 如果当前文件路径存在
            return;                             // 直接返回

        // 文件路径不存在
        //  创建文件目录
        try
        {
            std::filesystem::create_directories(_filepath); // 创建文件目录
        }
        catch (std::filesystem::filesystem_error const &e)
        {
            std::cerr << e.what() << '\n';
        }
    }
    virtual void Execute(const std::string &text) override
    {
        std::string content = text;
        if (_enable)
        {
            // 保存到文件中
            std::string file = _filepath + _filename; // 文件的相对路径 + 名称
            std::ofstream ofs(file, std::ios::app);   // 打开文件以追加方式
            if (!ofs.is_open())
            {
                // 没有打开文件
                std::cerr << "open file fail..." << std::endl;
                return;
            }
            // 打开文件成功
            content += '\n'; // 文本行
            ofs << content;
            ofs.close(); // 关闭文件
            std::cout << "step 2: save text to file done..." << std::endl;
        }

        // 判断下一个任务节点是否存在
        if (_next)
        {
            _next->Execute(content);
        }
        else
        {
            std::cout << "Chain Of Responsibility Done..." << std::endl;
        }
    }
};

// 任务三：备份（超过一定）
// 在Linux中一般的备份不是将内容拷贝一份，而是直接重命名
// 原因：重命名 mv是原子操作
// 原名称："text.log" —> "text.log.时间戳"
const int defaultfmaxline = 5;
class HandlerBackup : public HandlerText
{
private:
    std::string _filepath;
    std::string _filename;
    int _maxline = defaultfmaxline;

public:
    HandlerBackup(const std::string &filepath = defaultfilepath, const std::string &filename = defaultfilename)
        : _filepath(filepath), _filename(filename)
    {
    }
    virtual void Execute(const std::string &text) override
    {
        // 从第三任务开始，任务的处理重点就不在文本内容了，而在于文件
        std::string suffix = std::to_string(time(nullptr)); // 我们需要将处理的文件后缀传给后面的任务
        if (_enable)
        {
            // 先判断是否超过了限制范围
            std::cout << "check range of file..." << std::endl;
            std::string file = _filepath + _filename;
            if (isOutofRange(file))
            {
                // 就进行备份
                std::cout << "file is out of range, start to backup..." << std::endl;
                std::string backup_file = file + '.' + suffix; // 备份的文件名
                // TODO
                std::filesystem::rename(file, backup_file); // 备份 —> 改文件名
                std::cout << "step 3: backup file done..." << std::endl;

                // 只有完成了备份才会解决是否进行打包
                if (_next) // 判断下一个任务节点是否存在
                {
                    _next->Execute(suffix);
                }
                else
                {
                    std::cout << "Chain Of Responsibility Done..." << std::endl;
                }
            }
            std::cout << "range of file is OK!" << std::endl;
        }
    }

private:
    bool isOutofRange(const std::string &file)
    {
        std::ifstream ifs(file); // 读取文件
        if (!ifs.is_open())
        {
            std::cerr << "check file fail... because open file fail..." << std::endl;
            return false;
        }
        int lines = 0;
        std::string line;
        while (std::getline(ifs, line))
        {
            ++lines;
        }
        ifs.close();
        return lines > _maxline;
    }
};

// 任务四: 将备份的文件进行打包 并且 删除原来的文件
// 打包原来的文件：需要进行系统调用：exec*
class HandlerPackage : public HandlerText
{
private:
    std::string _filepath;
    std::string _filename;

public:
    HandlerPackage(const std::string &filepath = defaultfilepath, const std::string &filename = defaultfilename)
        : _filepath(filepath), _filename(filename)
    {
    }

public:
    virtual void Execute(const std::string &suffix) override
    {
        if (_enable)
        {
            std::string src_filename = _filename + '.' + suffix; //源文件名
            std::string backup_file = _filepath + src_filename;  // 备份的文件路径 + 名称
            std::string tar_filename = src_filename + ".tgz";    //打包文件名
            // 打包任务交给子进程
            pid_t pid = fork();
            if (pid == 0)
            {
                // child
                // 对文件进行打包
                //  ./tmp/text.log.时间戳 —> ./tmp/text.log.时间戳.tgz

                // 1、更改工作路径：打包一个文件需要在当前文件的目录中才可以完成我们想要的打包
                std::filesystem::current_path(_filepath);
                // 2、程序替换，exec*
                execlp("tar", "tar", "-czf", tar_filename.c_str(), src_filename.c_str(), nullptr);

                // 如果走到这里，说明程序替换失败 —> 原因：程序替换之后代码不会向后执行
                exit(1);
            }
            // parent
            int status = 0;
            pid_t rid = waitpid(pid, &status, 0);
            if(rid > 0)
            {
                if(WIFEXITED(status) && WEXITSTATUS(status) == 0)
                {
                    // 打包成功，删除源文件 —> 需要带路径
                    std::cout << "step 4: package file done..." << std::endl;
                    std::filesystem::remove(backup_file);
                }
            }

        }
        // 判断下一个任务节点是否存在
        if (_next)
        {
            _next->Execute(suffix);
        }
        else
        {
            std::cout << "Chain Of Responsibility Done..." << std::endl;
        }
    }
};

// 入口类
class Enter
{
public:
    Enter(const std::string &filepath = defaultfilepath, const std::string &filename = defaultfilename)
    {
        // 指向对象：make_shared就是new对象 + 赋值给指针
        _format = std::make_shared<HandlerFormat>();
        _save = std::make_shared<HandlerSave>(filepath, filename);
        _backup = std::make_shared<HandlerBackup>(filepath, filename);
        _package = std::make_shared<HandlerPackage>(filepath, filename);

        // 确定责任链
        _format->SetNext(_save);
        _save->SetNext(_backup);
        _backup->SetNext(_package);
    }
    void Able(bool isformat, bool issave, bool isbackup, bool ispackage)
    {
        isformat ? _format->Enable() : _format->Disable();
        isformat ? _save->Enable() : _save->Disable();
        isformat ? _backup->Enable() : _backup->Disable();
        isformat ? _package->Enable() : _package->Disable();
    }
    void Run(const std::string &text)
    {
        _format->Execute(text);
    }

private:
    std::shared_ptr<HandlerText> _format;  // 任务一：格式化
    std::shared_ptr<HandlerText> _save;    // 任务二：保存
    std::shared_ptr<HandlerText> _backup;  // 任务三：备份
    std::shared_ptr<HandlerText> _package; // 任务四：打包
};